package com.wish.nio;

import com.wish.bio.ServerHandle;
import com.wish.common.MyUtils;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;


/**
 * 使用NIO中非阻塞I/O编写服务器处理程序，大体上可以分为下面三个步骤： 1. 向Selector对象注册感兴趣的事件 2.
 * 从Selector中获取感兴趣的事件 3. 根据不同的事件进行相应的处理
 *
 * @author tantexian
 * @since 2016/6/24
 */
public class NIORobotServer {

    private static Selector selector;


    public static void main(String[] args) {
        final int port = args.length >= 1 ? Integer.parseInt(args[0]) : MyUtils.getDefaultPort();

        try {
            // 创建Selector对象
            selector = Selector.open();

            // 创建可选择通道，并配置为非阻塞模式
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);

            // 绑定通道到指定端口
            serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024);

            // 向Selector中注册感兴趣的事件(对于ServerSocketChannel通道来说，我们唯一可以指定的参数就是OP_ACCEPT)
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);


            /**
             * 启动线程，从Selector中获取感兴趣的事件，即开始监听，进入内部循环
             */
            new Thread(new Runnable() {
                public void run() {
                    System.out.println(
                        "I,m a smart robot and wait for you ask. [I'm listen on port " + port + "]\n");
                    while (true) {
                        try {
                            // 阻塞到至少有一个通道在你注册的事件上就绪了(超时时间为1000ms,每隔1超时唤醒执行一次)
                            // 返回的int值表示有多少通道已经就绪
                            selector.select(1000);
                            // 获取已选择键集（selected key set）中的就绪通道
                            Set<SelectionKey> selectionKeys = selector.selectedKeys();
                            Iterator<SelectionKey> iterator = selectionKeys.iterator();
                            SelectionKey selectionKey = null;
                            // 通过遍历这个已选择的键集合来访问就绪的通道
                            while (iterator.hasNext()) {
                                selectionKey = iterator.next();

                                // 根据各个键所对应的通道的就绪事件，进行对应的处理
                                HandlerSelectionKey(selectionKey);

                                // 处理完毕则移除掉
                                iterator.remove();
                            }
                        }
                        catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
        catch (IOException e) {
            try {
                selector.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        }
    }


    /**
     * 根据各个键所对应的通道的就绪事件，进行对应的处理
     * 
     * @author tantexian
     * @since 2016/7/7
     * @params
     */
    private static void HandlerSelectionKey(SelectionKey selectionKey) {
        try {
            if (selectionKey.isValid() && selectionKey.isAcceptable()) {
                // 相当于完成了TCP的三次握手，TCP物理链路正式建立
                ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
                SocketChannel socketChannel = serverSocketChannel.accept();
                socketChannel.configureBlocking(false);
                socketChannel.register(selector, SelectionKey.OP_READ);
            }

            if (selectionKey.isReadable()) {
                SocketChannel readAdnWriteSocketChannel = (SocketChannel) selectionKey.channel();
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                int read = readAdnWriteSocketChannel.read(byteBuffer);
                if (read > 0) {// 读取到了字节，进行相应处理
                    // 将缓冲区limit设置为position，将position设置为0，用于后续对缓冲区读取操作
                    byteBuffer.flip();
                    byte[] bytes = new byte[byteBuffer.remaining()];
                    byteBuffer.get(bytes);
                    String msg = new String(bytes);
                    // 重构复用BIO中示例代码
                    String answer = MyUtils.getAnswer(msg);
                    sendAnswer2Client(readAdnWriteSocketChannel, answer);
                    System.out.println("I'm receive the question is: " + msg + ", and the answer is: " + answer);
                }
                else if (read < 0) {
                    // 说明链路已关闭，关闭readAdnWriteSocketChannel 释放资源
                    /*selectionKey.cancel();
                    readAdnWriteSocketChannel.close();*/
                }
                else if (read == 0) {
                    // 说明当前没有可读字节，继续开始下一次while循环
                }

            }
        }
        catch (IOException e) {
            selectionKey.cancel();
            e.printStackTrace();
        }

    }

    private static void sendAnswer2Client(SocketChannel readAdnWriteSocketChannel, String answer) throws IOException {
        byte[] answerBytes = answer.getBytes();
        ByteBuffer writeBuffer = ByteBuffer.allocate(answerBytes.length);
        writeBuffer.put(answerBytes);
        writeBuffer.flip();
        readAdnWriteSocketChannel.write(writeBuffer);
    }
}
